#include <xbyak/xbyak.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stack>
#include <fstream>
#include <iostream>



class Brainfuck : public Xbyak::CodeGenerator {
private:
  enum Direction { B, F };
  const char *toStr(int labelNo, Direction dir) {
    static char num[64];
    snprintf(num, sizeof(num), "%c%d", dir == B ? 'B' : 'F', labelNo);
    return num;
  }

public:

  Brainfuck(std::string s) : CodeGenerator(50000) {
    using namespace Xbyak;
    const Reg32& pPutchar(esi);
    const Reg32& pGetchar(edi);
    const Reg32& stack(ebp);
    const Address cur = dword [stack];
    push(ebp); // stack
    push(esi);
    push(edi);
    const int _P = 4 * 3;
    mov(pPutchar, ptr[esp + _P + 4]); // putchar
    mov(pGetchar, ptr[esp + _P + 8]); // getchar
    mov(stack, ptr[esp + _P + 12]); // stack
    int labelNo = 0;
    std::stack<int> keepLabelNo;

    for(int i = 0; i < s.size(); i++) {
      switch (s[i]) {
      case '+':
      case '-':
	    s[i] == '+' ? inc(cur) : dec(cur);
      break;
      case '>':
      case '<':
	  add(stack, 4 * (s[i] == '>' ? 1: -1));
      break;
      case '.':
	push(cur);
	call(pPutchar);
	pop(eax);
	break;
      case ',':
	call(pGetchar);
	mov(cur, eax);
	break;
      case '[':
	L(toStr(labelNo, B));
	mov(eax, cur);
	test(eax, eax);
	jz(toStr(labelNo, F), T_NEAR);
	keepLabelNo.push(labelNo++);
	break;
      case ']':
	{
	  int no = keepLabelNo.top(); keepLabelNo.pop();
	  jmp(toStr(no, B));
	  L(toStr(no, F));
	}
	break;
      default:
	break;
      }
    }
    pop(edi);
    pop(esi);
    pop(ebp);
    ret();
  }
};

static const char* Cadd = "草泥马";    // +
static const char* Csub = "玛勒戈壁";  // -
static const char* Cgt = "卧草";      // >
static const char* Clt = "河蟹";      // <
static const char* Cdot = "卧槽泥马";  // .
static const char* Ccomma = "胡萝卜"; // ,
static const char* Cbegin = "啊";    // [
static const char* Cend = "嘿";      // ]

std::string cvt(char* filename)
{
  char* buffer = NULL;
  FILE* file = NULL;
  long filesize, copysize;
  std::string s;

  file = fopen(filename, "rb");

  if(file == NULL){
    fprintf(stderr, "Can`t open file %s", filename);
    exit(1);
  }
  fseek(file, 0, 2);
  filesize = ftell(file);
  rewind(file);

  buffer = (char*)malloc(filesize + 1);
  if(buffer == NULL){
    fprintf(stderr, "No more memory");
    exit(2);
  }
  copysize = fread(buffer, 1, filesize, file);
  buffer[filesize] = 0;
  if(copysize != filesize){
    fprintf(stderr, "Not read the whole file");
    exit(3);
  }
  fclose(file);

  char* p = buffer;
  while(1) {
    if(p - buffer >= filesize) break;

    if(strncmp(p, Cadd, strlen(Cadd)) == 0) {
      s += '+';
      p += strlen(Cadd);
    }
    else if(strncmp(p, Csub, strlen(Csub)) == 0) {
      s += '-';
      p += strlen(Csub);
    }
    else if(strncmp(p, Cgt, strlen(Cgt)) == 0) {
      s += '>';
      p += strlen(Cgt);
    }
    else if(strncmp(p, Clt, strlen(Clt)) == 0) {
      s += '<';
      p += strlen(Clt);
    }
    else if(strncmp(p, Cdot, strlen(Cdot)) == 0) {
      s += '.';
      p += strlen(Cdot);
    }
    else if(strncmp(p, Ccomma, strlen(Ccomma)) == 0) {
      s += ',';
      p += strlen(Ccomma);
    }
    else if(strncmp(p, Cbegin, strlen(Cbegin)) == 0) {
      s += '[';
      p += strlen(Cbegin);
    }
    else if(strncmp(p, Cend, strlen(Cend)) == 0) {
      s += ']';
      p += strlen(Cend);
    }
    else if(*p == ' ' || *p == '\r' || *p == '\t' || *p == '\n'){
      p++;
    }
    else
      p++;
  }

  return s;
}

void dump(const uint8_t *code, size_t size)
{
  puts("#include <stdio.h>\n"
       "static int stack[32768];\n"
       "static const unsigned char code[] = {");
  for (size_t i = 0; i < size; i++) {
    printf("0x%02x,", code[i]); if ((i % 16) == 15) putchar('\n');
  }
  puts("\n};");
#ifdef __linux__
  puts("#include <unistd.h>");
  puts("#include <sys/mman.h>");
#endif
  puts("main()\n{");
#ifdef __linux__
  puts("\tlong pageSize = sysconf(_SC_PAGESIZE) - 1;");
  puts("\tmprotect((void*)code, (sizeof(code)   pageSize) & ~pageSize, PROT_READ | PROT_EXEC);");
#endif
  puts(
       "\t((void (*)(void*, void*, int *))code)("
       "\t\t(void*)putchar, (void*)getchar, stack);\n"
       "}"
       );
}

int main(int argc, char *argv[])
{
  std::string s;

  if (argc == 1) {
    fprintf(stderr, "bf filename.bf [0|1]\n");
    return 1;
  }

  int mode = argc == 3 ? atoi(argv[2]) : 0;

  s = cvt(argv[1]);

  Brainfuck bf(s);

  if(mode == 0) {
    static int stack[32768];
    ((void (*)(void*, void*, int *)) bf.getCode())
      ( (void *) putchar, (void *) getchar, stack);
  } 
  else {
    dump(bf.getCode(), bf.getSize());
  }
  return 0;
}
